package cn.z2huo.demo.spring.transactional.service;

import cn.z2huo.core.util.IdUtils;
import cn.z2huo.demo.mapper.user.UserMapper;
import cn.z2huo.demo.model.dataobject.user.UserDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 *
 * @author z2huo
 */
@Slf4j
@Service
public class ServiceA {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private ServiceB serviceB;

    @Transactional(propagation = Propagation.NESTED)
    public void a_nested_b_nested_exception() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNestedException(userDO2);
    }

    @Transactional(propagation = Propagation.NESTED)
    public void a_nested_b_nested_exception_catch() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNestedExceptionCatch(userDO2);
    }

    @Transactional(propagation = Propagation.NESTED)
    public void a_nested_b_nested_exception_catch_in_a() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
        try {
            serviceB.insertNestedException(userDO2);
        } catch (Exception e) {
            log.error("serviceB insert nested exception, catch in A", e);
        }
    }

    @Transactional(propagation = Propagation.NESTED)
    public void a_nested_exception_b_nested() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNested(userDO2);

        int a = 1/0;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_never() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("never " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNever(userDO2);
    }

    public void a_none_b_not_supported() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("not supported " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNotSupported(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_not_supported() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("not supported " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNotSupported(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_not_supported_exception() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("not supported " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNotSupportedException(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_nested() {
        UserDO userDO = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNested(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_nested_exception() {
        UserDO userDO = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("nested exception" + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNestedException(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_nested_exception_catch() {
        UserDO userDO = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("nested catch" + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNestedExceptionCatch(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_nested_exception_catch_in_a() {
        UserDO userDO = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("nested exception" + RandomStringUtils.randomAlphabetic(8));
        try {
            serviceB.insertNestedException(userDO2);
        } catch (Exception e) {
            log.error("serviceB insert nested exception, catch in A", e);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_exception_b_nested() {
        UserDO userDO = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO()
                .setId(IdUtils.nextId())
                .setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNested(userDO2);

        int a = 1/0;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_mandatory() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("mandatory " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertMandatory(userDO2);
    }

    public void a_none_b_mandatory() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("mandatory " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertMandatory(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void a_required_new_exception_b_required_new() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertRequiredNew(userDO2);

        int a = 1/0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void a_required_new_b_required_new_exception() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertRequiredNewException(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void a_required_new_b_required_new_exception_catch() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertRequiredNewExceptionCatch(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void a_required_new_b_required_new_exception_catch_in_a() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
        try {
            serviceB.insertRequiredNewException(userDO2);
        } catch (Exception e) {
            log.error("serviceB insert required new exception, catch in A", e);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_required_exception() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertRequiredException(userDO2);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_required_exception_catch_in_a() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
        try {
            serviceB.insertRequiredException(userDO2);
        } catch (Exception e) {
            log.error("serviceB insert required exception, catch in A", e);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void a_required_b_required_exception_catch() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required exception catch " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertRequiredExceptionCatch(userDO2);
    }

    /**
     * 两个类中都没有事务，则在执行了 mapper 的 insert 方法之后，数据库中即可查询出来数据
     */
    public void a_none_b_none() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNoTransactional(userDO2);
    }

    /**
     * 该类中的无事务方法调用另一个类中的有事务方法，另一个类中的事务生效
     * 该类中调用完 mapper 的 insert 方法之后，数据库可以立刻查询出结果
     * 而 serviceB 中的有事务方法在执行了 mapper 的 insert 方法之后，数据库不会立刻查询出结果，直到 B 类的方法执行完成之后，事务提交，才会查询出结果
     */
    public void a_none_b_required() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertRequired(userDO2);
    }

    /**
     * 该类中的无事务方法调用另一个类中的无事务方法，另一个类中的无事务方法再调用其内部的有事务方法，事务不生效
     */
    public void a_none_b_none_required() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        userMapper.insert(userDO);

        UserDO userDO2 = new UserDO();
        userDO2.setId(IdUtils.nextId());
        userDO2.setUserName("none " + RandomStringUtils.randomAlphabetic(8));
        serviceB.insertNoTransactional_insertRequired(userDO2);
    }

    public void a_none_b_required_exception() {
        UserDO userDO = new UserDO();
        userDO.setId(IdUtils.nextId());
        userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
        // IDEA 异常提示
        // @Transactional self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime
//        this.insertRequiredException(userDO);
        userMapper.insert(userDO);

//        UserDO userDO2 = new UserDO();
//        userDO2.setId(IdUtils.nextId());
//        userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
//        serviceB.insertRequiredException(userDO2);
    }

    public void insertNoTransactional(UserDO userDO) {
        userMapper.insert(userDO);
    }

    public void insertNoTransactionalException(UserDO userDO) {
        userMapper.insert(userDO);
        int a = 1/0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertRequired(UserDO userDO) {
        userMapper.insert(userDO);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertRequiredException(UserDO userDO) {
        userMapper.insert(userDO);
        int a = 1 / 0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertRequiredNew(UserDO userDO) {
        userMapper.insert(userDO);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertRequiredNewException(UserDO userDO) {
        userMapper.insert(userDO);
        int a = 1 / 0;
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void insertMandatory(UserDO userDO) {
        userMapper.insert(userDO);
    }

}
